﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PlainOldVersionControl.Domain;
using System.IO;
using PlainOldVersionControl.Data.Base;
using PlainOldVersionControl.Data;
using PlainOldVersionControl.IViews.Base;

namespace PlainOldVersionControl.BusinessLogic.Base
{
    public class TreeBL : BaseBL
    {
        #region Constructor

        public TreeBL(Tree t) : base(t) { }

        #endregion

        #region Properties

        internal new Tree DomainObject
        {
            get { return base.DomainObject as Tree; }
        }

        #endregion

        #region Public Methods

        public override void Validate()
        {
            // TODO : put in validation
        }

        #endregion

        #region Public Static Methods

        public static void CreateCommit(ICommit view)
        {
            using (Tx.Start())
            {
                string path = PlainSettingsDA.GetRootPath();

                Tree newHeadTree = new Tree();
                Commit oldHead = GetById<Commit>(PlainSettingsDA.GetHead());
                                

                // put all objects in store
                PutAllObjectsInStore(path);

                // Commit Tree to database
                CommitTreeToDataBase(path, ref newHeadTree);
                
                Commit newHeadCommit = new Commit();
                newHeadCommit.Tree = newHeadTree;
                newHeadCommit.CreatedDate = DateTime.Now;
                newHeadCommit.Author = view.Author;
                newHeadCommit.Message = view.Message;
                newHeadCommit.Hash = ObjectStoreDA.AppendHash(newHeadTree.Hash, newHeadCommit.Author + newHeadCommit.Message);
                newHeadCommit.ParentCommit = oldHead;

                if (oldHead != null && oldHead.Tree != null && newHeadCommit.Tree.Hash == oldHead.Tree.Hash)
                    throw new ValidationException("Commit Has Same Tree Hash As Current Head.  Nothing Was Committed");

                new TreeBL(newHeadTree).Save();

                new CommitBL(newHeadCommit).Save();

                PlainSettingsDA.SetHead(newHeadCommit.Hash);

                view.Hash = newHeadCommit.Hash;
                view.CreatedDate = newHeadCommit.CreatedDate;
                view.ParentCommit = newHeadCommit.ParentCommit == null ? null : newHeadCommit.ParentCommit.Hash;
                view.TreeHash = newHeadCommit.Tree.Hash;

                // TODO : Email option goes here
            }
        }

        public static IList<CompareResult> CompareTwoTrees(string hashPrev, string hash)
        {
            using (Tx.Start())
            {
                IList<CompareResult> prev = new List<CompareResult>();
                IList<CompareResult> curr = new List<CompareResult>();

                Tree p = GetById<Tree>(hashPrev);
                Tree c = GetById<Tree>(hash);

                if (p == null)
                    throw new ValidationException("Previous Tree Hash Is Invalid");
                if (c == null)
                    throw new ValidationException("Current Tree Hash Is Invalid");

                FlattenTree(p, "", ref prev);
                FlattenTree(c, "", ref curr);

                IList<CompareResult> retVal = new List<CompareResult>();

                // all current compared to prev
                foreach (var blob in curr)
                {
                    CompareResult modify = prev.Where(x => x.File == blob.File && x.FileHash != blob.FileHash).SingleOrDefault();
                    if (modify != null)
                    {
                        CompareResult m = new CompareResult()
                        {
                            FileHash = blob.FileHash,
                            File = blob.File,
                            CompareType = CompareResult.CompareTypes.Modified
                        };
                        retVal.Add(m);
                        continue;
                    }    

                    CompareResult exact = prev.Where(x => x.FileHash == blob.FileHash && x.File == blob.File).SingleOrDefault();
                    if (exact == null)
                    {
                        CompareResult add = new CompareResult()
                        {
                            FileHash = blob.FileHash,
                            File = blob.File,
                            CompareType = CompareResult.CompareTypes.Added
                        };
                        retVal.Add(add);
                    }                                    
                }


                // all Prev vs current
                foreach (var blob in prev)
                {
                    CompareResult exact = curr.Where(x => //x.FileHash == blob.FileHash && 
                        x.File == blob.File).SingleOrDefault();

                    if (exact == null)
                    {
                        CompareResult del = new CompareResult()
                        {
                            CompareType = CompareResult.CompareTypes.Deleted,
                            File = blob.File,
                            FileHash = blob.FileHash
                        };
                        retVal.Add(del);
                    }                    
                }

                return retVal;
            }
        }

        public static string GetDirectoryHash(string path)  // TODO : Private
        {
            string retVal = string.Empty;

            foreach (string file in Directory.GetFiles(path)) // TODO : filter ignore
            {
                retVal = ObjectStoreDA.AppendHash(retVal, CalculateFilesHash(file));
            }

            foreach (string dir in Directory.GetDirectories(path)) // TODO : filter ignore
            {
                retVal = ObjectStoreDA.AppendHash(retVal, GetDirectoryHash(dir));
                retVal = ObjectStoreDA.AppendHash(retVal, Path.GetDirectoryName(dir));
            }

            return retVal;
        }

        #endregion

        #region Private Static Methods

        private static void FlattenTree(Tree tree, string pathSoFar, ref IList<CompareResult> result)
        {
            foreach (Blob b in tree.Blobs)
            {
                CompareResult r = new CompareResult()
                {
                    File = Path.Combine(pathSoFar, b.FileName),
                    FileHash = b.FileHash
                };
                result.Add(r);
            }

            foreach (Tree t in tree.Trees)
            {
                FlattenTree(t, Path.Combine(pathSoFar, t.DirectoryName), ref result);
            }
        }

        private static void PutAllObjectsInStore(string path) // TODO : Private
        {
            foreach (string file in Directory.GetFiles(path)) // TODO : filter ignore
                ObjectStoreDA.ArchiveFile(file, ObjectStoreDA.GetSHA1Hash(file));

            foreach (string dir in Directory.GetDirectories(path)) // TODO : filter ignore
                PutAllObjectsInStore(dir);
        }        

        private static string CommitTreeToDataBase(string path, ref Tree rootTree)  // TODO Private
        {

            string thisDirHash = GetDirectoryHash(path);
            Tree potTree = GetById<Tree>(thisDirHash);
            if (potTree != null) // Perfect Match
            {
                rootTree = potTree;
                return potTree.Hash;
            }
            else
            {
                string retVal = "";

                foreach (string file in Directory.GetFiles(path))
                {
                    Blob blob = new Blob();
                    string blobhash = ObjectStoreDA.GetSHA1Hash(file);
                    string fileName = Path.GetFileName(file);

                    retVal = ObjectStoreDA.AppendHash(retVal, CalculateFilesHash(file));

                    blob.Hash = ObjectStoreDA.AppendHash(blobhash, fileName);
                    blob.FileHash = blobhash;
                    blob.FileName = fileName;
                    blob.Tree = rootTree;
                    rootTree.Blobs.Add(blob);

                    new BlobBL(blob).Save();
                }

                foreach (string dir in Directory.GetDirectories(path))
                {
                    retVal = ObjectStoreDA.AppendHash(retVal, GetDirectoryHash(dir));
                    retVal = ObjectStoreDA.AppendHash(retVal, Path.GetDirectoryName(dir));

                    Tree nextTree = new Tree();
                    nextTree.Hash = CommitTreeToDataBase(dir, ref nextTree);
                    nextTree.DirectoryName = Path.GetDirectoryName(dir);
                    rootTree.Trees.Add(nextTree);

                    new TreeBL(nextTree).Save();
                }

                rootTree.DirectoryName = new DirectoryInfo(path).Name;// Path.GetDirectoryName(path);
                rootTree.Hash = thisDirHash;
                new TreeBL(rootTree).Save();

                return retVal;
            }
        }

        private static string CalculateFilesHash(string src)
        {
            string contentHash = ObjectStoreDA.GetSHA1Hash(src);
            return ObjectStoreDA.AppendHash(contentHash, Path.GetFileName(src));
        }

        #endregion

        public class CompareResult
        {
            public enum CompareTypes { Added, Deleted, Modified }
            public string File { get; set; }
            public string FileHash { get; set; }
            public CompareTypes CompareType { get; set; }

            public static string SimpleToString(IList<CompareResult> list)
            {
                StringBuilder changes = new StringBuilder();

                foreach (var delta in list)
                {
                    changes.Append(string.Format("{1} : {0}\r\n",
                        delta.File,
                        delta.CompareType.ToString().PadRight(10,' ')));
                }

                return string.Format("Adds + : {0}\r\nEdits ^ : {1}\r\nDeletes - : {2}\r\nChanged Files:\r\n {3}",
                                       list.Where(x => x.CompareType == CompareTypes.Added).Count(),
                                       list.Where(y => y.CompareType == CompareTypes.Modified).Count(),
                                       list.Where(z => z.CompareType == CompareTypes.Deleted).Count(),
                                       changes);


            }
        }
    }
}
